import Phaser from '../../lib/phaser3.esm.js'
import Carrot from '../items/carrot.js'

export default class Game extends Phaser.Scene {
  /** @type {Phaser.Physics.Arcade.Sprite} */
  player
  /** @type {Phaser.Physics.Arcade.StaticGroup} */
  platforms
  /** @type {Phaser.Physics.Arcade.Group} */
  carrots
  /** @type {Phaser.Types.Input.Keyboard.CursorKeys} */
  cursors
  /** @type {number} */
  carrotsCollected
  /** @type {Phaser.GameObjects.Text} */
  carrotsCollectedText

  constructor () {
    super('game') // name of the scene
  }
  // Phaser hook called before preload
  init (data) {
    this.carrotsCollected = 0
  }
  // Phaser hook to load assets
  preload () {
    // relative path to index.html
    this.load.setBaseURL('assets')
    this.load.image('background', 'bg_layer1.png')
    this.load.image('platform', 'ground_grass.png')
    this.load.image('bunny-stand', 'bunny1_stand.png')
    this.load.image('bunny-jump', 'bunny1_jump.png')
    this.load.image('carrot', 'carrot.png')
    this.load.audio('jump', 'sfx/phaseJump1.ogg')
    this.load.audio('collect', 'sfx/powerUp2.ogg')
    // init keyboard control
    this.cursors = this.input.keyboard.createCursorKeys()
  }
  // Phaser hook to create sprites
  create () {
    const { width, height } = this.scale
    this.add.image(width * 0.5, height * 0.5, 'background') // set background on center
        .setScrollFactor(1, 0) // x: 1, y: 0 background never scrolls

    this.createPlatforms()
    this.createPlayer()
    this.createCarrots()
    this.createTexts()
    this.physics.add.collider(this.platforms, this.player)
    this.physics.add.collider(this.platforms, this.carrots)
    this.physics.add.overlap(
      this.player,
      this.carrots,
      (player, carrot) => {
        this.killCarrot(carrot)
        this.sound.play('collect')
        ++this.carrotsCollected
        this.carrotsCollectedText.text = `Carrots: ${this.carrotsCollected}`
      }
    )
  }
  // Phaser hook to execute each frame
  update (t, dt) {
    const scrollY = this.cameras.main.scrollY
    const bottom = scrollY + this.scale.height + 150
    // recycle platforms
    this.platforms.children.iterate(platform => {
      if (platform.y > bottom) {
        platform.y = scrollY - Phaser.Math.Between(50, 100)
        platform.x = Phaser.Math.Between(80, 400)
        platform.body.updateFromGameObject() // refresh the change on y
        this.addCarrotAbove(platform) // add carrot to the new platform
      }
    })
    // recycle carrots
    this.carrots.children.iterate(carrot => {
      if (carrot.active && carrot.y > bottom) {
        this.killCarrot(carrot)
      }
    })
    this.updatePlayer()
  }
  createPlatforms () {
    this.platforms = this.physics.add.staticGroup()
    const hskip = 150
    const extra = 1
    const count = Math.ceil(this.scale.height / hskip) + extra
    for (let i = 0; i < count; ++i) {
      const x = Phaser.Math.Between(80, 400) // random x
      const y = hskip * i
  
      /** @type {Phaser.Physics.Arcade.Sprite} */
      const platform = this.platforms.create(x, y, 'platform')
      platform.scale = 0.5
      // refresh the change on scale
      // platform is static so update is necessary
      platform.body.updateFromGameObject()
    }
  }
  createPlayer () {
    this.player = this.physics.add.sprite(240, 320, 'bunny-stand')
      .setScale(0.5).setOrigin(0.5)
    // let camera follow player,
    // inside a deadzone the camera won't move
    this.cameras.main.startFollow(this.player)
    this.cameras.main.setDeadzone(this.scale.width * 1.5)
    // let player jump through
    const check = this.player.body.checkCollision
    check.up = check.left = check.right = false
  }
  createCarrots () {
    this.carrots = this.physics.add.group({ classType: Carrot })
    //this.carrotsCollected = 0
  }
  createTexts () {
    const style = { color: '#000', fontSize: 24 }
    this.carrotsCollectedText = this.add.text(240, 10, 'Carrots: 0', style)
        .setScrollFactor(0)
        .setOrigin(0.5, 0) // top-centered
  }
  updatePlayer () {
    // decide player velocity
    this.player.setVelocityX(0)
    if (this.player.body.touching.down) {
      // jump
      this.player.setVelocityY(-500)
      this.player.setTexture('bunny-jump')
      this.sound.play('jump')
      //console.log(this.player.y)
      if (this.player.y < -999) {
        this.verticalWrap()
      }
    } else if (this.cursors.left.isDown) {
      this.player.setVelocityX(-200)
    } else if (this.cursors.right.isDown) {
      this.player.setVelocityX(200)
    }
    if (this.player.body.velocity.y > 0 && this.player.texture.key !== 'bunny-stand') {
      this.player.setTexture('bunny-stand')
    }
    this.horizontalWrap(this.player) // wraps the player
    // to lose
    const deadline = 200 + Math.max(
      ...this.platforms.getChildren().map(platform => platform.y)
    )
    if (this.player.y > deadline) {
      this.scene.start('gameover', { collected: this.carrotsCollected })
    }
  }
  /**
   * @param {Phaser.GameObjects.Sprite} sprite 
   */
  horizontalWrap (sprite) {
    const halfWidth = sprite.displayWidth * 0.5
    const leftMost = -halfWidth
    const rightMost = this.scale.width + halfWidth
    if (sprite.x < leftMost) {
      sprite.x = rightMost
    } else if (sprite.x > rightMost) {
      sprite.x = leftMost
    }
  }
  // move everything so that y won't go to -infinity
  verticalWrap () {
    const offset = 999
    this.player.y += offset
    this.carrots.children.iterate(carrot => {
      carrot.y += offset
    })
    this.platforms.children.iterate(platform => {
      platform.y += offset
      platform.body.updateFromGameObject()
    })
  }
  /**
   * reuse carrot sprite
   * @param {Phaser.GameObjects.Sprite} sprite
   */
  addCarrotAbove (sprite) {
    const y = sprite.y - sprite.displayHeight // ≈ half sprite height and half carrot height
    /** @type {Phaser.Physics.Arcade.Sprite} */
    const carrot = this.carrots.get(sprite.x, y, 'carrot')
    carrot.setActive(true)
    carrot.setVisible(true)
    this.add.existing(carrot)
    // update the physics body size ??
    carrot.body.setSize(carrot.width, carrot.height)
    this.physics.world.enable(carrot)
    return carrot
  }
  /**
   * @param {Carrot} carrot
   */
  killCarrot (carrot) {
    this.carrots.killAndHide(carrot)
    this.physics.world.disableBody(carrot.body)
  }
}